#include "_shm.h"
#include "rcs_print.hh"
#include <stdio.h> /* NULL */
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>

#if defined(qnx) && !defined(USE_POSIX_SHAREDMEM)
#define USE_POSIX_SHAREDMEM 1
#endif

#ifdef USE_POSIX_SHAREDMEM
#include <fcntl.h>
#include <sys/mman.h>
#else
#include <sys/shm.h>
#endif

#include <string.h>

static int shmems_created_list[100];
static int shmems_created_list_initialized = 0;

shm_t* rcs_shm_open(key_t key, size_t size, int oflag, /* int mode */...)
{
    va_list ap;
    int mode;
    int shmflg = 0;
    shm_t* shm;
#ifdef USE_POSIX_SHAREDMEM
    int existed_before = 0;
#if HAVE_FSTAT
    struct stat statbuf;
#endif
#else
    struct shmid_ds shared_mem_info;

    int pid;
    int i;
#endif

    va_start(ap, oflag);
    if (oflag)
    {
        mode = va_arg(ap, int);
        shmflg |= mode;
    }
    va_end(ap);

#ifdef USE_POSIX_SHAREDMEM
    rcs_print_debug(PRINT_SHARED_MEMORY_ACTIVITY, "rcs_shm_open(key=%d(0x%X),size=%d(0x%X),oflag=%d)\n", key, key, size, size, oflag);

    if (key == 0)
    {
        rcs_print_error("rcs_shm_open(%d(0x%X), %d(0x%X), %d(0x%X)): error\n", key, key, size, size, oflag, oflag);
        rcs_print_error("RCS Shared Memory key may not be zero.\n");
        return NULL;
    }

    shm = (shm_t*)calloc(sizeof(shm_t), 1);
    if (NULL == shm)
    {
        rcs_print_error("rcs_shm_open: calloc failed\n");
        return NULL;
    }
    shm->create_errno = 0;
    shm->addr = NULL;
    shm->key = key;
    shm->size = size;
#ifdef POSIX_SHMEM_NAME_PREFIX
    strncpy(shm->name, POSIX_SHMEM_NAME_PREFIX, 64);
    sprintf(shm->name + strlen(shm->name), "/rcs_shm%d", key);
#else
    sprintf(shm->name, "/rcs_shm%d", key);
#endif

    shm->id = 0;
    errno = 0;

    if (oflag)
    {
        oflag = O_CREAT;
        shm->id = shm_open(shm->name, O_RDWR, 0700);
    }

    /* Create a new memory object */
    if (shm->id <= 0)
    {
        shm->id = shm_open(shm->name, oflag | O_RDWR, 0700);
        if (shm->id == -1)
        {
            rcs_print_error("shm_open(%s,%d(0x%X),%d(0x%X)) failed:%s %d\n", shm->name, oflag | O_RDWR, oflag | O_RDWR, mode, mode, strerror(errno), errno);
            shm->create_errno = errno;
            return shm;
        }
        existed_before = (oflag == 0);
    }
    else
    {
        existed_before = 1;
    }

    shm->created = (oflag != 0);

    /* Set the memory object's size */
    if (!existed_before)
    {
        if (ftruncate(shm->id, size + 16) == -1)
        {
            rcs_print_error("ftruncate(%d,%d): %s %d\n", shm->id, (size + 16), strerror(errno), errno);
            shm->create_errno = errno;
            return shm;
        }
    }
#if HAVE_FSTAT
    else
    {
        if (-1 == fstat(shm->id, &statbuf))
        {
            rcs_print_error("fstat failed. (errno=%d) %s\n", errno, strerror(errno));
            shm->create_errno = errno;
            return shm;
        }
        if (statbuf.st_size != size + 16)
        {
            rcs_print_error("Shared memory buffer %s already exists but has the wrong size of % d instead of the expected size of % d. \n ",
                            shm->name,
                            statbuf.st_size,
                            size + 16);
            shm->create_errno = -1;
            return shm;
        }
    }
#endif

    /* Map the memory object */
    shm->addr = mmap(0, size + 16, PROT_READ | PROT_WRITE, MAP_SHARED, shm->id, 0);
    if (shm->addr == MAP_FAILED)
    {
        rcs_print_error("mmap(0,%d,PROT_READ | PROT_WRITE, MAP_SHARED,%d,0) failed: %s %d\n", shm->id, size, strerror(errno), errno);
        shm->create_errno = errno;
    }
    shm->size = size;
    if (oflag & O_CREAT && !existed_before)
    {
        *((int*)((char*)shm->addr + size)) = 0;
    }
    else
    {
        (*((int*)((char*)shm->addr + size)))++;
    }

    return (shm);
#else

    rcs_print_debug(PRINT_SHARED_MEMORY_ACTIVITY, "rcs_shm_open(key=%d(0x%X),size=%zu(0x%zX),oflag=%d)\n", key, key, size, size, oflag);

    if (key == 0)
    {
        rcs_print_error("rcs_shm_open(%d(0x%X), %zu(0x%zX), %d(0x%X)): error\n", key, key, size, size, oflag, oflag);
        rcs_print_error("RCS Shared Memory key may not be zero.\n");
        return NULL;
    }
#if defined(O_CREAT)
#if O_CREAT != IPC_CREAT
    if ((oflag & O_CREAT) && !(oflag & IPC_CREAT))
    {
        oflag &= ~(O_CREAT);
        oflag |= IPC_CREAT;
    }
#endif
#endif

    if (oflag)
    {
        shmflg |= IPC_CREAT;
    }

    shm = (shm_t*)calloc(sizeof(shm_t), 1);
    if (NULL == shm)
    {
        rcs_print_error("rcs_shm_open: calloc failed\n");
        return NULL;
    }
    shm->create_errno = 0;
    shm->addr = NULL;
    shm->key = key;
    errno = 0;

    shm->size = size;

    if ((shm->id = shmget(key, (int)size, shmflg)) == -1)
    {
        shm->create_errno = errno;
        rcs_print_error("shmget(%d(0x%X),%zd,%d) failed: (errno = %d): %s\n", key, key, size, shmflg, errno, strerror(errno));
        switch (errno)
        {
        case EEXIST:
            rcs_print_error("A shared memory buffer for this key already exists.\n");
            break;

        case EINVAL:
            rcs_print_error("Either the size is too big or the shared memory buffer already exists but is of the wrong size.\n");
            break;

        case ENOSPC:
            rcs_print_error("The system imposed limit on the maximum number of shared memory segments has been exceeded.\n");
            break;

        case ENOENT:
            rcs_print_error("No shared memory buffer exists for this key and the IPC_CREAT was not given.\n");
            break;
        }
        return (shm);
    }

    /* map shmem area into local address space */
    shmflg = 0;
    shmflg &= ~SHM_RDONLY;
    if ((shm->addr = (void*)shmat(shm->id, 0, shmflg)) == (void*)-1)
    {
        shm->create_errno = errno;
        rcs_print_error("shmat(%d,0,%d) failed:(errno = %d): %s\n", shm->id, shmflg, errno, strerror(errno));
        rcs_print_error("key = %d (0x%X)\n", key, key);
        shm->addr = NULL;
        return (shm);
    }

    /* Check to see if I am the creator of this shared memory buffer. */
    if (shmctl(shm->id, IPC_STAT, &shared_mem_info) < 0)
    {
        rcs_print_error("shmctl error: %d %s\n", errno, strerror(errno));
        return shm;
    }

    /* If oflag was not set this process couldn't be the creator. */
    if (!oflag)
    {
        return shm;
    }

    if (!shmems_created_list_initialized)
    {
        memset(shmems_created_list, 0, 100 * sizeof(int));
        shmems_created_list_initialized = 1;
    }
    else
    {
        for (i = 0; i < 100; i++)
        {
            if (shmems_created_list[i] == key)
            {
                return shm;
            }
        }
    }

    pid = (int)getpid();
    if (pid <= 0)
    {
        rcs_print_error("getpid error: %d %s\n", errno, strerror(errno));
        return shm;
    }
    shm->created = (shared_mem_info.shm_cpid == pid);
#ifdef linux_2_4_0
    shm->created = 1;
#endif
    if (shm->created)
    {
        for (i = 0; i < 100; i++)
        {
            if (shmems_created_list[i] <= 0)
            {
                shmems_created_list[i] = shm->key;
                break;
            }
        }
    }
    return shm;
#endif
}

int rcs_shm_close(shm_t* shm)
{
#ifdef USE_POSIX_SHAREDMEM
    int nattch;
    if (shm == 0)
    {
        return -1;
    }
    if (shm->addr > 0)
    {
        nattch = rcs_shm_nattch(shm);
        (*((int*)((char*)shm->addr + shm->size)))--;
        if (munmap(shm->addr, shm->size + 16) == -1)
        {
            rcs_print_error("munmap(%p,%d) failed. %s %d\n", shm->addr, shm->size, strerror(errno), errno);
            return -1;
        }
        shm->addr = NULL;
        if (shm->id > 0)
        {
            if (close(shm->id) == -1)
            {
                rcs_print_error("close(%d) failed. %s %d\n", shm->id, strerror(errno), errno);
            }
        }
        if (nattch <= 1)
        {
            shm_unlink(shm->name);
        }
        shm->id = 0;
    }
#else
    struct shmid_ds shared_mem_info;

    int i;

    /* check for invalid ptr */
    if (shm == NULL)
        return -1;

    rcs_print_debug(PRINT_SHARED_MEMORY_ACTIVITY,
                    "rcs_shm_close(shm->key=%d(0x%X),shm->size=%zu(0x%zX),shm->addr=%p)\n",
                    shm->key,
                    shm->key,
                    shm->size,
                    shm->size,
                    shm->addr);

    /* detach from shmem */
    shmdt((char*)shm->addr);

    /* remove OS shmem if there are no attached processes */
    if (rcs_shm_nattch(shm) == 0)
    {
        shmctl(shm->id, IPC_RMID, &shared_mem_info);
    }

    if (shm->created && shmems_created_list_initialized)
    {
        for (i = 0; i < 100; i++)
        {
            if (shmems_created_list[i] == shm->key)
            {
                shmems_created_list[i] = 0;
                break;
            }
        }
    }
#endif

    free(shm);

    return 0;
}

int rcs_shm_delete(shm_t* shm)
{
#ifdef USE_POSIX_SHAREDMEM
    if (shm == 0)
    {
        return -1;
    }
    if (shm->addr > 0)
    {
        (*((int*)((char*)shm->addr + shm->size)))--;
        if (munmap(shm->addr, shm->size + 16) == -1)
        {
            rcs_print_error("munmap(%p,%d) failed. %s %d\n", shm->addr, shm->size, strerror(errno), errno);
            return -1;
        }
        shm->addr = NULL;
        if (shm->id > 0)
        {
            if (close(shm->id) == -1)
            {
                rcs_print_error("close(%d) failed. %s %d\n", shm->id, strerror(errno), errno);
            }
        }
        shm->id = 0;
    }
    shm_unlink(shm->name);
#else
    struct shmid_ds shared_mem_info;

    /* check for invalid ptr */
    if (shm == NULL)
        return -1;

    /* detach from shmem */
    shmdt((char*)shm->addr);

    /* remove OS shmem regardless of whether there are attached processes */
    shmctl(shm->id, IPC_RMID, &shared_mem_info);
#endif

    free(shm);

    return 0;
}

int rcs_shm_nattch(shm_t* shm)
{
#ifdef USE_POSIX_SHAREDMEM
    if (shm == 0)
    {
        return -1;
    }
    if (shm->addr == 0)
    {
        return -1;
    }
    return *((int*)(((char*)shm->addr) + shm->size)) + 1;
#else
    struct shmid_ds shared_mem_info;
    int err;
    /* check for invalid ptr */
    if (shm == NULL)
        return -1;

    /* get the status of shared memory */
    err = shmctl(shm->id, IPC_STAT, &shared_mem_info);

    if (err == -1)
    {
        rcs_print_error("rcs_shm_nattch: shmctl failed: %s\n", strerror(errno));
        return 0;
    }

    return shared_mem_info.shm_nattch;
#endif
}
